﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Reflection;

namespace POVRay {
    static class POVRayStringRepresentation {
        public static string POVRayString(this bool b) {
            return b ? "on" : "off";
        }

        public static string POVRayString(this float f) {
            return f.ToString(CultureInfo.InvariantCulture);
        }

        public static string POVRayString(this int i) {
            return i.ToString(CultureInfo.InvariantCulture);
        }

        public static string POVRayString(this System.Object obj, int level) {
            var customAttributes = obj.GetType().GetCustomAttributes(typeof(PRBlock), true);
            if (customAttributes.Length == 0) {
                return ((Block)obj).ToString(level);
            }

            var block = customAttributes.First() as PRBlock;

            if (block == null) {
                return (obj as Block).ToString(level + 1);
            }

            var blockName = block.Name;

            var propertyInfos = obj.GetType().GetProperties();
            Array.Sort(propertyInfos, (x, y) => {
                var first = x as PropertyInfo;
                var second = y as PropertyInfo;

                bool firstIsDerived = first.DeclaringType != obj.GetType();
                bool secondIsDerived = second.DeclaringType != obj.GetType();

                if (firstIsDerived == secondIsDerived) {
                    if (first.MetadataToken < second.MetadataToken)
                        return -1;
                    else if (first.MetadataToken > second.MetadataToken)
                        return 1;
                    else
                        return 0;
                } else {
                    if (secondIsDerived)
                        return -1;
                    else
                        return 1;
                }
            });

            StringBuilder sb = new StringBuilder();
            if (blockName != null && blockName.Length > 0) {
                sb.Append(blockName);
                sb.Append(" ");
            }
            sb.Append("{");

            foreach (var propertyInfo in propertyInfos) {
                var prParams = propertyInfo.GetCustomAttributes(typeof(PRParam), true);
                if (prParams.Length == 0) {
                    continue;
                }

                var param = prParams.First() as PRParam;
                var prPropName = param.Name;
                var type = propertyInfo.PropertyType;
                var value = propertyInfo.GetValue(obj, null);
                var valueIsNullable = Nullable.GetUnderlyingType(type) != null;

                if (value != null) {
                    sb.Append(Scene.Indentation(level + 1));
                    if (prPropName != null && prPropName.Length > 0) {
                        if (param.HasValue || (!valueIsNullable && type == typeof(bool) && (bool)value)) {
                            sb.Append(prPropName);
                            sb.Append(" ");
                        }
                    }

                    if (param.HasValue) {
                        var prParamIntMap = propertyInfo.GetCustomAttributes(typeof(PRParamIntMap), true);

                        if (prParamIntMap.Length == 0) {
                            if (valueIsNullable == false) {
                                if (type == typeof(float)) {
                                    sb.Append(((float)value).POVRayString());
                                } else if (type == typeof(int)) {
                                    sb.Append(((int)value).POVRayString());
                                } else if (type == typeof(bool)) {
                                    sb.Append(((bool)value).POVRayString());
                                } else if ((value as Block) != null) {
                                    sb.Append((value as Block).ToString(level + 1));
                                } else {
                                    sb.Append(value.ToString());
                                }
                            } else {
                                var floatNullable = value as Nullable<float>;
                                var intNullable = value as Nullable<int>;
                                var boolNullable = value as Nullable<bool>;
                                var v2Nullable = value as Nullable<Vector2>;
                                var v3Nullable = value as Nullable<Vector3>;
                                var colorNullable = value as Nullable<Color>;

                                if (floatNullable != null && floatNullable.HasValue) {
                                    sb.Append(floatNullable.Value.POVRayString());
                                } else if (intNullable != null && intNullable.HasValue) {
                                    sb.Append(intNullable.Value.POVRayString());
                                } else if (boolNullable != null && boolNullable.HasValue) {
                                    sb.Append(boolNullable.Value.POVRayString());
                                } else if (v2Nullable != null && v2Nullable.HasValue) {
                                    sb.Append(v2Nullable.Value.ToString());
                                } else if (v3Nullable != null && v3Nullable.HasValue) {
                                    sb.Append(v3Nullable.Value.ToString());
                                } else if (colorNullable != null && colorNullable.HasValue) {
                                    sb.Append(colorNullable.Value.ToString());
                                }
                            }
                        } else {
                            int key = (int)value;
                            string mapValue = (prParamIntMap as PRParamIntMap[]).Where(p => p.Key == key).First().Value;
                            sb.Append(mapValue);
                        }
                    }

                    if (param.HasComma) {
                        sb.Append(",");
                    }

                    sb.Append(" ");
                }
            }

            sb.Append(Scene.Indentation(level) + "}");
            return sb.ToString();
        }
    }
}
